Final Result Preview
Check out the demo for the final result.
The Problem
Flash has some built-in components for video playback, but they're bloated, difficult to customize and can't accommodate intelligent sequential preloading of multiple video files or elegant transitions. Many developers have reported buggy behavior too. We could strip things all the way down to using the NetStream class directly, but that gets insanely cumbersome particularly because there are numerous bugs and inconsistencies there too.
The Solution
LoaderMax is a new AS3 loading system from GreenSock that does for loading what TweenMax and TweenLite did for tweening - makes things so much easier and more intuitive. Explaining all the features and benefits of using LoaderMax is beyond the scope of this article, but I'll mention just a few that are pertinent to our project:
- Load many files sequentially and report progress as a whole.
- Dynamic prioritizing. If the user clicks the "next" or "back" button several times quickly to skip to another video that hasn't loaded yet, LoaderMax makes it simple to prioritize that video immediately in the loading queue, maximizing the user's bandwidth and minimizing load time.
- Full playback control through ActionScript. LoaderMax's VideoLoader class not only loads the video file, but also provides advanced playback control including play, pause, volume, buffer progress, download progress, and playback progress reporting.
- Automatically adjust buffer. We can set the bufferTime initially and then if it empties during playback, VideoLoader can automatically analyze the speed at which the file has been loading and adjust the bufferTime accordingly to ensure that playback doesn't stop/start/stop/start on slow connections.
- Proportionally scale the video to fit and center its registration point.
- Avoid bugs. NetStream and the built-in components suffer from several bugs and inconsistencies that VideoLoader works around. Oh, and VideoLoader is about 1/5th the size of the FLVPlayback component.
- Read video data from XML. This can make updates and additions much easier.
Read more about LoaderMax on the GreenSock website.
Step 1: Overview of the Code and UI
Inside the provided Source zip folder you'll find source files to both a start and a finish file structure. This allows you to see the final results pre-compiled (finish). To follow the tutorial, begin with contents of the start folder.
Please note that video assets are best rendered at 960 x 540 either as a flv, f4v, mp4, or mov. The latest version of the flash player should be installed to also benefit from all the compression codecs Flash now recognizes. The latest version of the flash player can be found at http://get.adobe.com.

The Flash .fla file contains a full library of pre-designed custom video controls (movie clips and images) allowing a more granular focus on the AS3 code. Again, the goal of this tutorial is to allow a deeper understanding of LoaderMax (primarily its VideoLoader and XMLLoader classes) as well as some of the logic that drives a custom video player. Therefore I won't spend a lot of time explaining the .fla file.

A review of the Timeline layers in Flash. Each layer contains a MovieClip named accordingly.

Create a new ActionScript class named "Main" (Main.as) inside the ../src/ directory. Then open the publish settings dialogin the Flash file and change the publishing directories for both the .swf and .html files to the root directory of the bin folder.

In the ActionScript 3.0 settings dialog, add a source path to ../src/ so that Flash knows where to find our source code. Make the Document class "Main" (the file we created in the previous step).

(Follow this Quick Tip for more information on using a document class.)
Download the most recent GreenSock LoaderMax version and place the whole GreenSock com folder inside of the ../src/ directory. At the time of this writing, we are using version 1.62, updated 2010-10-07.
Step 2: The Main Document Class
Start by opening up the Main.as file. Let's add the import statements at the top for all the necessary classes and create 4 private variables that we'll need for tracking various values.
package {
import flash.display.MovieClip;
import flash.events.Event;
import flash.events.MouseEvent;
import com.greensock.TweenMax;
import com.greensock.events.LoaderEvent;
import com.greensock.loading.LoaderMax;
import com.greensock.loading.XMLLoader;
import com.greensock.loading.VideoLoader;
import flash.geom.Rectangle;
public class Main extends MovieClip {
//an array containing the VideoLoaders in the order they should be played
private var _videos:Array;
//keeps track of the VideoLoader that is currently playing
private var _currentVideo:VideoLoader;
//If true, the audio has been muted
private var _silentMode:Boolean;
//tracks whether or not the video was paused when the user moused-down on the scrubber. We must pause for scrubbing, but this tells us whether or not to playVideo() when the user releases the mouse.
private var _preScrubPaused:Boolean;
public function Main() {
}
}
}
Note: we'll be using TweenMax for doing the video crossfades, button tweens, and pretty much any animated effects.
Step 3: Test
Just simply compile. This will ensure that the project is set up correctly and the GreenSock classes are importing correctly. Not too much should be happening at this point but, being free from errors is imperative to the next steps.
If you do get an error please just go back and ensure the project is set up correctly following the directions in Step 1.
Step 4: XML Build the File
One of the neat features of LoaderMax is that it can load an XML file, recognizing LoaderMax-related nodes inside the XML file and automatically parse them, creating the necessary loader instances. In this case, we will be loading 3 videos, so create an XML document named videoList.xml and save it in the ../bin/xml/ directory. Then begin populating the XML with a <LoaderMax> node like this:
<?xml version="1.0" encoding="iso-8859-1"?> <videoList> <LoaderMax name="videoListLoader" maxConnections="1" prependURLs="assets/"> </LoaderMax> </videoList>
The <videoList> root node is insignificant. We could name it anything. The important part is the <LoaderMax> that contains a few attributes:
name- defining a name here makes it easy to reference inside ActionScript. For example, to get the LoaderMax instance that results from the<LoaderMax name="videoListLoader">node, all we'd need to do isLoaderMax.getLoader("videoListLoader");maxConnections- tells LoaderMax to only load one video at a time. Normally LoaderMax will try to load 2 files at a time to maximize overall loading speed, but since videos are typically very large and we want to make sure the first one gets all the bandwidth and loads as fast as possible, we set maxConnections to 1.prependURLs- prepends all of the URLs of the child loaders withassets/. This can be particularly useful if you have a large number of assets that all come from the same domain - you can just include the individual file names for each loader but in the<LoaderMax>wrapper, useprependURLsorreplaceURLTextto make global adjustments. Maybe you have a production server and a different deployment server - changing from one to the other could be as simple as changing theprependURLvalue.
Step 5: XML Adding Nodes
Next, let's add a <VideoLoader> node for each of our videos:
<?xml version="1.0" encoding="iso-8859-1"?> <videoList> <LoaderMax name="videoListLoader" maxConnections="1" prependURLs="assets/"> <VideoLoader name="videoID0" url="videoClip01.flv" height ="406" width="720" scaleMode="proportionalInside" centerRegistration="true" alpha="0" autoPlay="false" /> <VideoLoader name="videoID1" url="videoClip02.mp4" height ="406" width="720" scaleMode="proportionalInside" centerRegistration="true" alpha="0" autoPlay="false" /> <VideoLoader name="videoID2" url="videoClip03.flv" height ="406" width="720" scaleMode="proportionalInside" centerRegistration="true" alpha="0" autoPlay="false" /> </LoaderMax> </videoList>
Again, we can set various properties right away that LoaderMax will recognize:
name- once again, this simply makes it easier to reference individual loaders inside ActionScript.url- defines the URL for each VideoLoaderheight/width- optionally define a width and height that the video should be scaled to fit into.scaleMode- controls how the video is scaled to fit into the width/height. Options are:stretch,proportionalInside,proportionalOutside,widthOnly,heightOnly, andnone.centerRegistration- whentrue, the video's registration point will be located in its center. This can be useful if, for example, you plan to animate the scale of the video and want it to grow/shrink from the center instead of the upper left corner.alpha- the initial alpha of the video. Since we'll be doing crossfades, we want the initial alpha value to be 0.autoPlay- by default, videos will being playing as soon as they are buffered, but since we're preloading multiple videos that would be bad because we'd start to hear the audio from each as soon as they finished buffering. SettingautoPlaytofalsemakes sure the videos stay paused until we tell them to play.
Our XML document could contain other generic information too - it isn't limited to LoaderMax-related data. There are also many other attributes that can be used to control various aspects of the loaders. See the ASDocs for details.
Step 6: activate() VideoLoader and XMLLoader
Inside of the Main constructor function (in Main.as), we need to activate any loader types that we want LoaderMax to recognize when parsing the XML file. Why doesn't LoaderMax just activate them all automatically? Because one of the key design considerations in LoaderMax was keeping file size low. Allowing each type of loader to be activated individually makes the system far more efficient and can reduce file size by 60% or more. You only need to activate the loader types once in your swf, so we'll do it in our constructor. Just pass LoaderMax.activate() an array of the loader classes to activate:
public function Main() {
LoaderMax.activate([XMLLoader, VideoLoader]);
}
Step 7: Initialize the UI
Create a private function named initUI() that will set up the initial state of our user interface and prevent mouse interaction with a few elements (see the comments in the code for details). Call initUI() inside of the constructor function.
private function initUI():void {
//ignore mouse interaction with progressBar_mc so that clicks pass through to the loadingBar_mc whose listener handles skipping the video to that spot.
controlUI_mc.progressBar_mc.mouseEnabled = false;
//ignore mouse interaction with preloader_mc
preloader_mc.mouseEnabled = false;
//the "layer" blendMode makes the alpha fades cleaner (overlapping objects don't add alpha levels)
controlUI_mc.blendMode = "layer";
//set the progress and loading bars and the scrubber to the very beginning
controlUI_mc.progressBar_mc.width = controlUI_mc.loadingBar_mc.width = 0;
controlUI_mc.scrubber_mc.x = controlUI_mc.progressBar_mc.x;
//initially hide the user interface - autoAlpha:0 sets alpha to 0 and visible to false.
TweenMax.allTo([controlUI_mc, playPauseBigButton_mc, preloader_mc], 0, {autoAlpha:0});
}
Step 8: Load the XML
We'll use LoaderMax's XMLLoader to load our XML. The first parameter in the constructor is the URL where the XML file can be found. The second parameter is a generic object that contains various special property values - in this case we'll give our XMLLoader a name and tell it to call a function named xmlHandler() when it completes. By the way, defining onComplete:xmlHandler is simply a shorter way of doing addEventListener(LoaderEvent.COMPLETE, xmlHandler). It is easy to listen for many different kinds of events like PROGRESS, FAIL, ERROR, INIT, etc. but we'll keep our example simple and just worry about the onComplete. In a real-world application, it would be good to at least listen for the FAIL event too in case one of the URLs was incorrect or the server went down.
Our constructor should now look like this:
public function Main() {
LoaderMax.activate([XMLLoader, VideoLoader]);
initUI();
var xmlLoader:XMLLoader = new XMLLoader("xml/videoList.xml", {name:"videoList", onComplete:xmlHandler});
xmlLoader.load();
}
Step 9: Load the Videos
Now let's create our xmlHandler() method that gets called when the XML has finished loading. Remember, LoaderMax will automatically recognize the <LoaderMax> and <VideoLoader> nodes and create the corresponding instances so all we need to do is get the LoaderMax named "videoListLoader" and load() it. That will begin loading the first VideoLoader and as soon as that one finishes loading, it will automatically load the next, and so on until all of the nested VideoLoaders have loaded. Let's store the VideoLoaders in a _videos array to make it easy to figure out their order (we'll need that when the user clicks the "next" or "back" buttons). We can also start showing the first video - we'll build the showVideo() method soon.
private function xmlHandler(event:LoaderEvent):void {
//get the LoaderMax named "videoListLoaded" which was inside our XML
var queue:LoaderMax = LoaderMax.getLoader("videoListLoader");
//store the nested VideoLoaders in an array
_videos = queue.getChildren();
//start loading the queue of VideoLoaders (they will load in sequence)
queue.load();
//show the first video
showVideo(_videos[0]);
}
Step 10: Start building showVideo()
This is really the heart of the application. showVideo() accepts a single parameter - the VideoLoader that we want to show. It will need to handle the transition between any video that is currently playing and the new video that should start playing. For now, let's just focus on getting our first video playing.
private function showVideo(video:VideoLoader):void {
//set the _currentLoader variable so that it refers to the new video.
_currentVideo = video;
//start playing the video from its beginning
_currentVideo.gotoVideoTime(0, true);
//when we addChild() the VideoLoader's content, it makes it rise to the top of the stacking order
videoContainer_mc.addChild(_currentVideo.content);
//fade the VideoLoader's content alpha in. Remember, the "content" refers to the ContentDisplay Sprite that we see on the stage.
TweenMax.to(_currentVideo.content, 0.8, {autoAlpha:1});
}
Step 11: Test
Test the movie and make sure the first video begins playing. You won't see any buttons yet; we'll work on that next. All we care about right now is seeing the first video playing.
Step 12: Play/Pause Add Functionality
Our various interface elements like the play/pause button, scrubber, mute button, etc. affect the video that's currently playing (_currentVideo) but while the XML was loading, _currentVideo was null so in order to prevent null reference errors from being thrown, we simply don't hook up those interface elements until we set _currentVideo to a real VideoLoader. The user could click the play/pause button while the XML was loading, for example, and it would have no effect.
Now that we've got our first VideoLoader playing, let's call an activateUI() method where we'll put all the code that handles wiring up the user interface. We'll also build a little addListeners() utility function that allows us to add the same listener to multiple objects in an array with less code than it would take to manually add each redundant listener. For now, we will focus on adding the play/pause functionality:
private function addListeners(objects:Array, type:String, func:Function):void {
var i:int = objects.length;
while (i--) {
objects[i].addEventListener(type, func);
}
}
private function activateUI():void {
addListeners([controlUI_mc, videoContainer_mc, playPauseBigButton_mc], MouseEvent.ROLL_OVER, toggleControlUI);
addListeners([controlUI_mc, videoContainer_mc, playPauseBigButton_mc], MouseEvent.ROLL_OUT, toggleControlUI);
addListeners([controlUI_mc.playPauseButton_mc, playPauseBigButton_mc, videoContainer_mc], MouseEvent.CLICK, togglePlayPause);
}
Step 13: Play/Pause Toggle
Now let's build the toggleControlUI() method that fades in the user interface controls. We use the hitTestPoint() method to determine whether or not the mouse is over the video. TweenMax's autoAlpha feature is useful here - it is the same thing as tweening the alpha except that it also automatically toggles the visible property to false when alpha hits 0, and true when alpha isn't 0.
private function toggleControlUI(event:MouseEvent):void {
if (videoContainer_mc.hitTestPoint(mouseX, mouseY)) {
TweenMax.to(controlUI_mc, 0.3, {autoAlpha:1});
} else {
TweenMax.to(controlUI_mc, 0.3, {autoAlpha:0});
}
}
Step 14: Play/Pause Blurring and Dimming
The togglePlayPause() method not only alters the VideoLoader to playback state, but it also changes the user interface buttons to indicate the corresponding state. The playPauseBigButton_mc should either fade in or out, the playPauseButton_mc should skip to the correct label (either showing the pause button or play button), and for an extra nifty effect, we'll blur the video and dim it to a brightness of 0.5 whenever it is paused. Again, we use TweenMax to do the tweens.
private function togglePlayPause(event:MouseEvent=null):void {
_currentVideo.videoPaused = ! _currentVideo.videoPaused;
if (_currentVideo.videoPaused) {
TweenMax.to(playPauseBigButton_mc, 0.3, {autoAlpha:1});
controlUI_mc.playPauseButton_mc.gotoAndStop("paused");
TweenMax.to(videoContainer_mc, 0.3, {blurFilter:{blurX:6, blurY:6}, colorMatrixFilter:{brightness:0.5}});
} else {
TweenMax.to(playPauseBigButton_mc, 0.3, {autoAlpha:0});
controlUI_mc.playPauseButton_mc.gotoAndStop("playing");
TweenMax.to(videoContainer_mc, 0.3, {blurFilter:{blurX:0, blurY:0, remove:true}, colorMatrixFilter:{brightness:1, remove:true}});
}
}
Invoke activateUI() inside showVideo() and then test the movie and you should now be able to click on the video or the play/pause button to toggle the playback mode.
Step 15: Display the Loading Progress
We've got a black bar MovieClip underneath the green prgress bar MovieClip that we want to display the loading progress of the current video, so let's add a listener inside the showVideo() method:
_currentVideo.addEventListener(LoaderEvent.PROGRESS, updateDownloadProgress);
Next, we'll create the updateDownloadProgress() method that simply alters the loadingBar_mc's scaleX based on the VideoLoader's progress property which is always a value between 0 and 1 representing how much of the file has been loaded (bytesLoaded / bytesTotal). This will cause the loadingBar_mc to grow from a scaleX of 0 when the file hasn't loaded any bytes to a scaleX of 1 when it is fully loaded.
private function updateDownloadProgress(event:LoaderEvent=null):void {
controlUI_mc.loadingBar_mc.scaleX = _currentVideo.progress;
}
Step 16: Display the Playback Progress
Let's apply the same concept to the progressBar_mc so that it reflects the overall playback progress of the VideoLoader. Add a PLAY_PROGRESS listener inside the showVideo() method:
_currentVideo.addEventListener(VideoLoader.PLAY_PROGRESS, updatePlayProgress);
Create the updatePlayProgress() method but this time, we also want to update the current time value in the TextField to the right of the mute button (the one that says something like "00:09 / 00:15"). Let's build a force2Digits() method that we can feed a numeric value and have it spit back the two-digit version of it (for any values under 10, a "0" gets prepended). We'll also set the x property of the scrubber_mc so that it is always positioned right over the end of the progressBar_mc.
We'll add scrubbing functionality shortly.
private function force2Digits(value:Number):String {
return (value < 10) ? "0" + String(value) : String(value);
}
private function updatePlayProgress(event:LoaderEvent=null):void {
var time:Number = _currentVideo.videoTime;
var minutes:String = force2Digits(int(time / 60));
var seconds:String = force2Digits(int(time % 60));
controlUI_mc.currentTime_tf.text = minutes + ":" + seconds;
controlUI_mc.progressBar_mc.scaleX = _currentVideo.playProgress;
controlUI_mc.scrubber_mc.x = controlUI_mc.progressBar_mc.x + controlUI_mc.progressBar_mc.width;
}
Step 17: Scrubbing Add Functionality
First, let's add a MOUSE_DOWN listener to the scrubber_mc inside the activateUI() method:
controlUI_mc.scrubber_mc.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownScrubber);
When the user mouses-down on the scrubber_mc, we'll use ActionScript's startDrag() to make it draggable, defining a bounds Rectangle to ensure that it cannot be dragged outside of the progress bar area. Since the video should pause as soon as the user mouses-down on the scrubber, we'll utilize the _preScrubPaused variable to keep track of the paused state so that we know whether or not to playVideo() when the user releases the mouse.
private function mouseDownScrubber(event:MouseEvent):void {
_preScrubPaused = _currentVideo.videoPaused;
_currentVideo.videoPaused = true;
controlUI_mc.scrubber_mc.startDrag(false, new Rectangle(controlUI_mc.loadingBar_mc.x, controlUI_mc.loadingBar_mc.y, controlUI_mc.loadingBar_mc.width, 0));
stage.addEventListener(MouseEvent.MOUSE_UP, mouseUpScrubber);
stage.addEventListener(MouseEvent.MOUSE_MOVE, scrubToMouse);
}
Note: We added MOUSE_MOVE and MOUSE_UP listeners to the stage - don't add them to the scrubber_mc because the user may mouse-up over another area of the stage (outside the bounds we defined for the dragging). Adding the listeners to the stage ensures that those events will be dispatched regardless of where the user's mouse goes.
Step 18: Scrubbing scrubToMouse()
Now let's build the scrubToMouse() method that gets invoked every time the mouse moves:
private function scrubToMouse(event:MouseEvent):void {
controlUI_mc.progressBar_mc.width = controlUI_mc.mouseX - controlUI_mc.progressBar_mc.x;
_currentVideo.playProgress = controlUI_mc.progressBar_mc.scaleX;
}
All we're doing here is setting the width of the progressBar_mc so that its right edge is wherever the mouse is. We also set the VideoLoader's playProgress value accordingly so that the video skips to the appropriate spot. Remember, the playProgress value is always between 0 and 1 and indicates the overall playback progress of the video, so 0.5 would be halfway through the video. Immediately after the progressBar_mc's width been adjusted, we can use its scaleX as the VideoLoader's playProgress because they always correlate.
Step 19: Scrubbing mouseUpScrubber()
Next, we'll set up the mouseUpScrubber() method that gets invoked when the user releases their mouse after having moused-down on the scrubber. This means they're done scrubbing so we'll remove the stage listeners, stop dragging and set the playback state of the video:
private function mouseUpScrubber(event:MouseEvent):void {
stage.removeEventListener(MouseEvent.MOUSE_UP, mouseUpScrubber);
stage.removeEventListener(MouseEvent.MOUSE_MOVE, scrubToMouse);
controlUI_mc.scrubber_mc.stopDrag();
_currentVideo.videoPaused = _preScrubPaused;
}
Step 20: Activate the Mute Button
First, add a CLICK listener to the audio_mc button inside the activateUI() method:
controlUI_mc.audio_mc.addEventListener(MouseEvent.CLICK, toggleAudio);
Then, create a toggleAudio() function that toggles the _silentMode variable, sets the volume of the VideoLoader, and jumps the audio_mc.label MovieClip to the appropriate label:
private function toggleAudio(event:MouseEvent):void {
_silentMode = !_silentMode;
if (_silentMode) {
_currentVideo.volume = 0;
controlUI_mc.audio_mc.label.gotoAndStop("off");
} else {
_currentVideo.volume = 1;
controlUI_mc.audio_mc.label.gotoAndStop("on");
}
}
Step 21: When the Video Finishes Playing, Go to the Next Video
VideoLoader will dispatch a VIDEO_COMPLETE event when it finishes playing the video, so let's add a listener in the showVideo() method:
_currentVideo.addEventListener(VideoLoader.VIDEO_COMPLETE, nextVideo);
Our nextVideo() method must find the _currentVideo's index in the _videos array and then we'll add one to the index to determine the next video. However, if we've reached the end of the array, we'll loop back to the beginning:
private function nextVideo(event:Event):void {
var next:int = _videos.indexOf(_currentVideo) + 1;
if (next >= _videos.length) {
next = 0;
}
showVideo(_videos[next]);
}
Step 22: Transition Videos
We need to add some code to our showVideo() method to transition out the old video and transition in the new one. Of course if the new video is the same as the old/current one, we should do nothing. We need to remove the listeners from the old video, fade its alpha and volume and then rewind it back to the beginning so that when we fade it in again, it's already at the first frame. We can use an onComplete callback in our tween for that. Here is what the showVideo() method should look like after adding the transition code:
private function showVideo(video:VideoLoader):void {
//if the new video is the one that's currently showing, do nothing.
if (video == _currentVideo) {
return;
}
//The first time through, the _currentVideo will be null. That's when we need to activate the user interface
if (_currentVideo == null) {
activateUI(); //don't activate the UI until the first video is ready. This avoids errors when _currentVideo is null.
} else {
//remove the event listeners from the _currentVideo (which is now the old one that will be replaced)
_currentVideo.removeEventListener(LoaderEvent.PROGRESS, updateDownloadProgress);
_currentVideo.removeEventListener(VideoLoader.VIDEO_COMPLETE, nextVideo);
_currentVideo.removeEventListener(VideoLoader.PLAY_PROGRESS, updatePlayProgress);
//If the video is paused, we should togglePlayPause() so that the new video plays and the interface matches.
if (_currentVideo.videoPaused) {
togglePlayPause();
}
//fade the current (old) video's alpha out. Remember the VideoLoader's "content" refers to the ContentDisplay Sprite we see on the screen.
TweenMax.to(_currentVideo.content, 0.8, {autoAlpha:0});
//fade the current (old) video's volume down to zero and then pause and rewind the video (it will be invisible by that time).
TweenMax.to(_currentVideo, 0.8, {volume:0, onComplete:rewindAndPause, onCompleteParams:[_currentVideo]});
}
//now swap the _currentLoader variable so it refers to the new video.
_currentVideo = video;
//listen for PROGRESS events so that we can update the loadingBar_mc's scaleX accordingly
_currentVideo.addEventListener(LoaderEvent.PROGRESS, updateDownloadProgress);
//listen for a VIDEO_COMPLETE event so that we can automatically advance to the next video.
_currentVideo.addEventListener(VideoLoader.VIDEO_COMPLETE, nextVideo);
//listen for PLAY_PROGRESS events so that we can update the progressBar_mc's scaleX accordingly
_currentVideo.addEventListener(VideoLoader.PLAY_PROGRESS, updatePlayProgress);
//start playing the video from its beginning
_currentVideo.gotoVideoTime(0, true);
//always start with the volume at 0, and fade it up to 1 if necessary.
_currentVideo.volume = 0;
if (!_silentMode) {
TweenMax.to(_currentVideo, 0.8, {volume:1});
}
//when we addChild() the VideoLoader's content, it makes it rise to the top of the stacking order
videoContainer_mc.addChild(_currentVideo.content);
//fade the VideoLoader's content alpha in. Remember, the "content" refers to the ContentDisplay Sprite that we see on the stage.
TweenMax.to(_currentVideo.content, 0.8, {autoAlpha:1});
//update the progressBar_mc and loadingBar_mc
updateDownloadProgress();
updatePlayProgress();
}
And let's not forget our rewindAndPause() method:
private function rewindAndPause(video:VideoLoader):void {
video.pauseVideo();
video.gotoVideoTime(0);
}
Step 23: Activate the "Next" and "Back" Buttons
We already have a nextVideo() method that we can call when the "next" button gets clicked, but let's build a similar one for the "back" button:
private function previousVideo(event:Event):void {
var prev:int = _videos.indexOf(_currentVideo) - 1;
if (prev < 0) {
prev = _videos.length - 1;
}
showVideo(_videos[prev]);
}
In the activateUI() method, we'll add the CLICK event listeners to the next/back buttons:
controlUI_mc.next_mc.addEventListener(MouseEvent.CLICK, nextVideo); controlUI_mc.back_mc.addEventListener(MouseEvent.CLICK, previousVideo);
Step 24: Show the Preloader Animation While Buffering
What if we showVideo() a video that hasn't even started loading yet? This becomes increasingly likely because of the "next" and "back" buttons that allow the user to skip forwards/backwards in the video queue. In that case, we should make sure that the new video gets prioritized in the LoaderMax queue and that we show the peloader animation until it has buffered enough to begin playing. Towards the bottom of the showVideo() method, we'll add some code to handle that scenario:
//if the video hasn't fully loaded yet and is still buffering, show the preloader
if (_currentVideo.progress < 1 && _currentVideo.bufferProgress < 1) {
//when the buffer fills, we'll fade out the preloader
_currentVideo.addEventListener(VideoLoader.VIDEO_BUFFER_FULL, bufferFullHandler);
//prioritizing the video ensures that it moves to the top of the LoaderMax gueue and any other loaders that were loading are canceled to maximize bandwidth available for the new video.
_currentVideo.prioritize(true);
//play() the preloader and fade its alpha up.
preloader_mc.play();
TweenMax.to(preloader_mc, 0.3, {autoAlpha:1});
}
And of course we'll need to add the simple bufferFullHandler() method that just fades out the preloader and then stops its animation:
private function bufferFullHandler(event:LoaderEvent):void {
TweenMax.to(preloader_mc, 0.3, {autoAlpha:0, onComplete:preloader_mc.stop});
}
We can't forget to remove the VIDEO_BUFFER_FULL listener when transitioning to another video. We'll see that in the final showVideo() method in step 26.
Step 25: Refresh the Total Time Display
Create a refreshTotalTime() method whose sole purpose is to report the duration of the _currentVideo in the appropriate TextField. We'll call this method from inside the showVideo() later. It is virtually identical to the code in the updatePlayProgress() method that reports the current playhead position in terms of minutes/seconds.
private function refreshTotalTime(event:LoaderEvent=null):void {
var minutes:String = force2Digits(int(_currentVideo.duration / 60));
var seconds:String = force2Digits(int(_currentVideo.duration % 60));
controlUI_mc.totalTime_tf.text = minutes + ":" + seconds;
}
There is a chance that when we show a video, it may not have even loaded its metaData yet which contains its duration information. To determine if that's the case, we can simply check to see if the VideoLoader's metaData property is null. If so, we'll add an INIT event listener that invokes our refreshTotalTime() method (when the VideoLoader receives its metaData, it dispatches an INIT event). This is the code we'll add to the bottom of the showVideo() method:
refreshTotalTime();
if (_currentVideo.metaData == null) {
_currentVideo.addEventListener(LoaderEvent.INIT, refreshTotalTime);
}
Step 26: The Final showVideo() Method
Here it is in all its glory:
private function showVideo(video:VideoLoader):void {
//if the new video is the one that's currently showing, do nothing.
if (video == _currentVideo) {
return;
}
//The first time through, the _currentVideo will be null. That's when we need to activate the user interface
if (_currentVideo == null) {
activateUI(); //don't activate the UI until the first video is ready. This avoids errors when _currentVideo is null.
} else {
//remove the event listeners from the _currentVideo (which is now the old one that will be replaced)
_currentVideo.removeEventListener(LoaderEvent.PROGRESS, updateDownloadProgress);
_currentVideo.removeEventListener(VideoLoader.VIDEO_COMPLETE, nextVideo);
_currentVideo.removeEventListener(VideoLoader.PLAY_PROGRESS, updatePlayProgress);
_currentVideo.removeEventListener(VideoLoader.VIDEO_BUFFER_FULL, bufferFullHandler);
_currentVideo.removeEventListener(LoaderEvent.INIT, refreshTotalTime);
//If the video is paused, we should togglePlayPause() so that the new video plays and the interface matches.
if (_currentVideo.videoPaused) {
togglePlayPause();
}
//fade out the preloader and then stop() it. If the new video needs to display the preloader, that's okay because the fade-in tween we create later will overwrite this one.
TweenMax.to(preloader_mc, 0.3, {autoAlpha:0, onComplete:preloader_mc.stop});
//fade the current (old) video's alpha out. Remember the VideoLoader's "content" refers to the ContentDisplay Sprite we see on the screen.
TweenMax.to(_currentVideo.content, 0.8, {autoAlpha:0});
//fade the current (old) video's volume down to zero and then pause and rewind the video (it will be invisible by that time).
TweenMax.to(_currentVideo, 0.8, {volume:0, onComplete:rewindAndPause, onCompleteParams:[_currentVideo]});
}
//now swap the _currentLoader variable so it refers to the new video.
_currentVideo = video;
//listen for PROGRESS events so that we can update the loadingBar_mc's scaleX accordingly
_currentVideo.addEventListener(LoaderEvent.PROGRESS, updateDownloadProgress);
//listen for a VIDEO_COMPLETE event so that we can automatically advance to the next video.
_currentVideo.addEventListener(VideoLoader.VIDEO_COMPLETE, nextVideo);
//listen for PLAY_PROGRESS events so that we can update the progressBar_mc's scaleX accordingly
_currentVideo.addEventListener(VideoLoader.PLAY_PROGRESS, updatePlayProgress);
//if the video hasn't fully loaded yet and is still buffering, show the preloader
if (_currentVideo.progress < 1 && _currentVideo.bufferProgress < 1) {
//when the buffer fills, we'll fade out the preloader
_currentVideo.addEventListener(VideoLoader.VIDEO_BUFFER_FULL, bufferFullHandler);
//prioritizing the video ensures that it moves to the top of the LoaderMax gueue and any other loaders that were loading are canceled to maximize bandwidth available for the new video.
_currentVideo.prioritize(true);
//play() the preloader and fade its alpha up.
preloader_mc.play();
TweenMax.to(preloader_mc, 0.3, {autoAlpha:1});
}
//start playing the video from its beginning
_currentVideo.gotoVideoTime(0, true);
//always start with the volume at 0, and fade it up to 1 if necessary.
_currentVideo.volume = 0;
if (!_silentMode) {
TweenMax.to(_currentVideo, 0.8, {volume:1});
}
//when we addChild() the VideoLoader's content, it makes it rise to the top of the stacking order
videoContainer_mc.addChild(_currentVideo.content);
//fade the VideoLoader's content alpha in. Remember, the "content" refers to the ContentDisplay Sprite that we see on the stage.
TweenMax.to(_currentVideo.content, 0.8, {autoAlpha:1});
//update the total time display
refreshTotalTime();
//if the VideoLoader hasn't received its metaData yet (which contains duration information), we should set up a listener so that the total time gets updated when the metaData is received.
if (_currentVideo.metaData == null) {
_currentVideo.addEventListener(LoaderEvent.INIT, refreshTotalTime);
}
//update the progressBar_mc and loadingBar_mc
updateDownloadProgress();
updatePlayProgress();
}
Step 27: Make the Bar Clickable for Instant Seeking
The user should be able to click anywhere on the loading bar to skip to that spot in the video immediately. This is very similar to the scrubbing feature except that the user doesn't have to drag the scrubber to the spot - they can just click there. We can add a CLICK event listener to our loadingBar_mc but one problem is that our progressBar_mc is on top of it, so CLICK events don't pass through to the loadingBar_mc if the user clicks on an area that overlaps with the progressBar_mc. This is why we set the mouseEnabled property of progressBar_mc to false in step 7 (in the initUI() method). This allows mouse events to pass through it.
We already built a scrubToMouse() method in a previous step which serves our purpose here perfectly, so we'll just set up our listener to point there. Add this line inside the activateUI() method:
controlUI_mc.loadingBar_mc.addEventListener(MouseEvent.CLICK, scrubToMouse);
Step 28: Finishing Touches: Add Button Effects
Let's make several of our UI elements act more like buttons by setting buttonMode to true and mouseChildren to false by adding this code inside activateUI():
var controls:Array = [controlUI_mc.playPauseButton_mc, playPauseBigButton_mc, controlUI_mc.back_mc, controlUI_mc.next_mc, controlUI_mc.audio_mc, controlUI_mc.scrubber_mc];
var i:int = controls.length;
while (i--) {
controls[i].buttonMode = true;
controls[i].mouseChildren = false;
}
Next, we'll add ROLL_OVER / ROLL_OUT listeners to our "next", "back" and audio mute buttons so that their text gets tinted to white on rollover. We can reuse the same handler for all of the buttons and simply use the event.target to identify which one to tween (actually, since we named the TextField inside each one "label", we'll target event.target.label). This code should be added inside the activateUI() method:
addListeners([controlUI_mc.back_mc, controlUI_mc.audio_mc, controlUI_mc.next_mc], MouseEvent.ROLL_OVER, blackRollOverHandler); addListeners([controlUI_mc.back_mc, controlUI_mc.audio_mc, controlUI_mc.next_mc], MouseEvent.ROLL_OUT, blackRollOutHandler);
And now for the handlers:
private function blackRollOverHandler(event:MouseEvent):void {
TweenMax.to(event.target.label, 0.3, {tint:0xFFFFFF});
}
private function blackRollOutHandler(event:MouseEvent):void {
TweenMax.to(event.target.label, 0.3, {tint:null});
}
Step 29: Grow/Shrink the Play/Pause Button on RollOver/Out
Let's scale the play/pause button up when the user rolls over it, and then back down on rollout. First, add this code to activateUI():
controlUI_mc.playPauseButton_mc.addEventListener(MouseEvent.ROLL_OVER, growPlayPause); controlUI_mc.playPauseButton_mc.addEventListener(MouseEvent.ROLL_OUT, shrinkPlayPause);
Then build the handlers:
private function growPlayPause(event:MouseEvent):void {
TweenMax.to(event.target, 0.2, {scaleX:1.3, scaleY:1.3});
}
private function shrinkPlayPause(event:MouseEvent):void {
TweenMax.to(event.target, 0.2, {scaleX:1, scaleY:1});
}
Step 30: Compile
You're done! Test your movie and enjoy your fancy new XML-driven video jukebox.
Conclusion
We covered a lot of ground in this tutorial and we ended up with a video player that offers snappy performance, elegant transitions and an easily customizable interface. Also, the fact that it's XML-driven makes it simple to add more videos, change the order, etc. The LoaderMax system made loading a snap and even took care of controlling video playback.
Frankly we haven't even scratched the surface of what the LoaderMax system is capable of for other types of files, so I would encourage you to check out the web site to learn more. And if you're not using TweenMax or TweenLite, you're missing out. If you have questions related to GreenSock tools, head over to the forums!

Active Premium
Interested in seeing more? Join Premium today and get a creative education from the professionals.
